/**
 * Licensed under the Apache License Version 2.0.
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package net.interknit.knitter.fw;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 *
 */
public class CustomTypeFactory implements TypeAdapter, TypeInstantiator, IOAdapter
{
	private static CustomTypeFactory instance = null;

	public static CustomTypeFactory getInstance()
	{
		return instance != null ? instance : (instance = new CustomTypeFactory());
	}

	private final List<Class<?>> customTypeClasses = new ArrayList<Class<?>>();
	private final List<TypeAdapter> customTypeAdapters= new ArrayList<TypeAdapter>();
	private TypeInstantiator customTypeInstantiator = new DefaultTypeInstantiator();
	private IOAdapter customIOAdapter = new DefaultIOAdapter();

	protected CustomTypeFactory()
	{
		try
		{
			String p = CustomProperties.getCustomProperty(CustomProperties.TYPE_INSTANTIATOR_PROPERTY_NAME);

			customTypeInstantiator = (TypeInstantiator) Class.forName(p).newInstance();

			System.out.println("Interknit: Custom TypeInstantiator will be used: " + p);
		}
		catch(Exception e)
		{
			System.err.println("Interknit: Custom TypeInstantiator could not created. " +
					"Will use DefaultTypeInstantiator instead.");
		}

		try
		{
			String p = CustomProperties.getCustomProperty(CustomProperties.IO_ADAPTER_PROPERTY_NAME);

			customIOAdapter = (IOAdapter) Class.forName(p).newInstance();

			System.out.println("Interknit: Custom IOAdapter will be used: " + p);
		}
		catch(Exception e)
		{
			System.err.println("Interknit: Custom IOAdapter could not created. " +
					"Will use DefaultIOAdapter instead.");
		}
	}

	void addCustomTypeClasses(Class<?>[] customTypeClassArray)
	{
		if(customTypeClassArray != null && customTypeClassArray.length > 0)
			this.customTypeClasses.addAll(Arrays.asList(customTypeClassArray));
	}

	public void addCustomTypeAdapter(TypeAdapter customTypeAdapter)
	{
		if(customTypeAdapter != null)
			this.customTypeAdapters.add(customTypeAdapter);
	}

	public void setCustomTypeInstantiator(TypeInstantiator customTypeInstantiator)
	{
		if(customTypeInstantiator != null)
			this.customTypeInstantiator = customTypeInstantiator;
	}

	public void setCustomIOAdapter(IOAdapter customIOAdapter)
	{
		if(customIOAdapter != null)
			this.customIOAdapter = customIOAdapter;
	}

	@Override
	public java.lang.Object deserialize(java.lang.String stringForm)
	{
		try
		{
			java.lang.Object nativeForm = null;

			// first iterate over the custom type adapters and
			// try deserializing the string into a custom type.
			// if that results in a non-null custom type (nativeForm)
			// use that as the return.
			for(TypeAdapter ta : customTypeAdapters)
			{
				nativeForm = ta.deserialize(stringForm);
				if(nativeForm != null) break;
			}

			if(nativeForm != null) return nativeForm;

			// if none of the custom type adapters could deserialize the
			// string parameter into a custom type, use the pre-defined
			// custom type classes and iterate over them to attemp
			// deserialization.  if that results in a non-null custom type
			// (nativeForm) use that as the return type.
			for(Class<?> customTypeClass : customTypeClasses)
			{
				Method m = customTypeClass.getMethod("deserialize", java.lang.String.class);
				nativeForm = m.invoke(null, stringForm);

				if(nativeForm != null) break;
			}

			// finally, if none match, send the stringForm as is
			return nativeForm != null ? nativeForm : stringForm;
		}
		catch(Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	@Override
	public java.lang.String serialize(java.lang.Object nativeForm)
	{
		try
		{
			java.lang.String stringForm = null;

			for(TypeAdapter ta : customTypeAdapters)
			{
				stringForm = ta.serialize(nativeForm);
				if(stringForm != null) break;
			}

			if(stringForm != null) return stringForm;

			for(Class<?> customTypeClass : customTypeClasses)
			{
				Method m = customTypeClass.getMethod("serialize", java.lang.Object.class);
				stringForm = (java.lang.String) m.invoke(null, nativeForm);

				if(stringForm != null) break;
			}

			// finally, if none match, send the String value of nativeForm
			return stringForm != null ? stringForm : String.valueOf(nativeForm);
		}
		catch(Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	@Override
	public <T> T instantiate(Class<T> clazz)
	{
		try
		{
			return customTypeInstantiator.instantiate(clazz);
		}
		catch(Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	@Override
	public <T extends Reader> T toReader(char[] value, Class<T> valueType)
	throws IOException
	{
		return customIOAdapter.toReader(value, valueType);
	}

	@Override
	public <T extends InputStream> T toInputStream(byte[] value, Class<T> valueType)
	throws IOException
	{
		return customIOAdapter.toInputStream(value, valueType);
	}
}
